package com.yang.service.impl;


import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yang.domain.dto.ScrollResult;
import com.yang.domain.dto.UserDTO;
import com.yang.domain.entity.Blog;
import com.yang.domain.entity.Follow;
import com.yang.domain.entity.User;
import com.yang.enums.AppHttpCodeEnum;
import com.yang.mapper.BlogMapper;
import com.yang.mapper.FollowMapper;
import com.yang.mapper.UserBlogMapper;
import com.yang.mapper.UserMapper;
import com.yang.service.IBlogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yang.constants.SystemConstants;
import com.yang.utils.UserHolder;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import static com.yang.constants.RedisConstants.*;
import static com.yang.enums.AppHttpCodeEnum.USER_NO_LOGIN;
import static com.yang.enums.LikeTypeEnum.LIKE;
import static com.yang.enums.LikeTypeEnum.UNLIKE;


/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author yang
 * @since 2022-10-22
 */
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private BlogMapper blogMapper;
    @Resource
    private FollowMapper followMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private UserBlogMapper userBlogMapper;
    /**
     * 点赞的lua脚本
     */
    private DefaultRedisScript<Long> LIKE_SCRIPT;

    /**
     * 取消点赞的lua脚本
     */
    private DefaultRedisScript<Long> UNLIKE_SCRIPT;

    @PostConstruct
    public void init() {
        LIKE_SCRIPT = new DefaultRedisScript<>();
        LIKE_SCRIPT.setResultType(Long.class);
        LIKE_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("like.lua")));

        UNLIKE_SCRIPT = new DefaultRedisScript<>();
        UNLIKE_SCRIPT.setResultType(Long.class);
        UNLIKE_SCRIPT.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlike.lua")));
    }

    @Override
    public Blog queryBlogById(Long id) {

        Blog blog = getById(id);

        Assert.notNull(blog,AppHttpCodeEnum.BLOG_NULL.getMsg());

        queryBlogUser(blog);
        isBlogLiked(blog);
        setBlogLiked(blog);

        return blog;
    }
    @Override
    public void likeBlog(Long blogId) {

        Long userId = UserHolder.getUser().getId();

        String key = BLOG_LIKED_KEY + blogId;

        String statusKey = BLOG_LIKED_KEY_MAP + blogId;

        Object status = stringRedisTemplate.opsForHash().get(statusKey, userId.toString());

        if (status == null || status.equals(UNLIKE.getCode())){

           stringRedisTemplate.execute(LIKE_SCRIPT, Collections.emptyList(),
                   statusKey, userId.toString(), LIKE.getCode(),
                   key, String.valueOf(System.currentTimeMillis()), BLOG_LIKED_CNT, blogId.toString(), LIKE.getData()
           );

        }else {
            stringRedisTemplate.execute(UNLIKE_SCRIPT,Collections.emptyList(),
                    statusKey,userId.toString(),UNLIKE.getCode(),key,BLOG_LIKED_CNT,blogId.toString(),UNLIKE.getData());
        }

    }

    @Override
    public Long saveBlog(Blog blog) {

        Long userId = UserHolder.getUser().getId();
        blog.setUserId(userId);

        boolean success = save(blog);

        Assert.isTrue(success,AppHttpCodeEnum.ADD_BLOG_LOSER.getMsg());

        CompletableFuture.runAsync(() -> pushBlogsToFollowers(userId, blog.getId().toString()));

        return blog.getId();

    }

    /**
     * 推送消息给粉丝
     * @param userId 用户id
     * @param blogId 博客id
     */
    private void pushBlogsToFollowers(Long userId,String blogId){
        // 查询作者粉丝
        List<Follow> follows = followMapper.selectAllFollow(userId);

        // 拼接出key
        follows.forEach(follow -> {

            Long id = follow.getUserId();

            String key = FEED_KEY + id;

            stringRedisTemplate.opsForZSet().add(key,blogId,System.currentTimeMillis());
        });
    }

    @Override
    public List<Blog> queryHotBlog(Integer current) {
        //分页查询博客信息
        List<Blog> records = blogMapper.pageFindAll(current,SystemConstants.MAX_PAGE_SIZE);

        //将作者信息和是否点赞添加进去进行完善
        records.forEach(blog -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
            this.setBlogLiked(blog);
        });

        return records;
    }

    @Override
    public List<UserDTO> queryBlogLikes(Long id) {
        String key = BLOG_LIKED_KEY + id;

        Set<String> range5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);

        if (range5 == null || range5.isEmpty()){
            return Collections.emptyList();
        }

        List<Long> idList = range5.stream().map(Long::valueOf).collect(Collectors.toList());

        return userMapper.selectUsersById(idList);

    }

    @Override
    public ScrollResult queryBlogOfFollow(Long max, Integer offset) {
        //1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        String key  = FEED_KEY + userId;

        //2.查询收件箱
        Set<ZSetOperations.TypedTuple<String>> typedTuples =
                stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);

        //3.收件箱非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return new ScrollResult();
        }

        //4.解析收件箱数据
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;

        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            // 4.1.获取id
            ids.add(Long.valueOf(Objects.requireNonNull(tuple.getValue())));
            // 4.2.获取分数(时间戳）
            long time = Objects.requireNonNull(tuple.getScore()).longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }

        os = minTime == max ? os : os + offset;
        // 5.根据id查询blog
        String idStr = StrUtil.join(",", ids);

        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

        for (Blog blog : blogs) {
            // 5.1.查询blog有关的用户
            queryBlogUser(blog);
            // 5.2.查询blog是否被点赞
            isBlogLiked(blog);
        }

        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);

        return r;
    }

    @Override
    public List<Blog> queryBlogByUserId(Integer current, Long id) {

        //根据用户查看他的博客，按照点赞量排行
        return blogMapper.queryBlogPages(current, id, SystemConstants.MAX_PAGE_SIZE);
    }

    @Override
    public List<Blog> queryMyBlog(Integer current) {

        UserDTO user = UserHolder.getUser();

        Page<Blog> page = query().eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));

        page.getRecords().forEach(this::setBlogLiked);

        return page.getRecords();
    }

    /**
     * 查询出作者信息，补全到博客里面
     * @param blog 博客
     */
    private void queryBlogUser(Blog blog) {
        Long userId = blog.getUserId();
        User user = userMapper.getUserInFoById(userId);
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }

    /**
     * 设置redis的点赞数
     * @param blog 博客
     */
    private void setBlogLiked(Blog blog) {

        Double score = stringRedisTemplate.opsForZSet().score(BLOG_LIKED_CNT, blog.getId().toString());

        if (score != null){
            blog.setLiked(score.intValue());
            return;
        }

        int likeCount = blogMapper.selectLikeById(blog.getId());
        blog.setLiked(likeCount);
        stringRedisTemplate.opsForZSet().add(BLOG_LIKED_CNT,blog.getId().toString(),likeCount);
    }

    /**
     * 判断当前用户有没有对这篇博客点过攒
     * @param blog 博客
     */
    private void isBlogLiked(Blog blog) {

        UserDTO user = UserHolder.getUser();

        Assert.notNull(user,USER_NO_LOGIN.getMsg());

        Long userId = user.getId();

        String key = BLOG_LIKED_KEY_MAP + blog.getId();
        Object status = stringRedisTemplate.opsForHash().get(key, userId.toString());

        if (status == null){
            status = userBlogMapper.selectStatusById(user.getId(),blog.getId());
        }

        blog.setIsLike(LIKE.getCode().equals(status));
    }
}
